package com.ytjj.qmyx.admin.service.data.impl;

import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Maps;
import com.ytjj.common.exception.ApiException;
import com.ytjj.common.service.RedisService;
import com.ytjj.qmyx.admin.constants.data.MongoCollectionNameConstants;
import com.ytjj.qmyx.admin.dao.StatisticsMapper;
import com.ytjj.qmyx.admin.model.data.mongo.PageStatistics;
import com.ytjj.qmyx.admin.model.data.response.PageStatisticsResponse;
import com.ytjj.qmyx.admin.model.response.ProductDataResponse;
import com.ytjj.qmyx.admin.service.data.PageStatisticsService;
import com.ytjj.qmyx.admin.utils.CalendarUtil;
import com.ytjj.qmyx.mall.mapper.ProductQueryHistoryMapper;
import com.ytjj.qmyx.mall.model.ProductQueryHistory;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.util.StringUtil;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationOptions;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;

/**
 *
 *  *
 **/
@Service
@Slf4j
public class PageStatisticsServiceImpl implements PageStatisticsService {

    @Resource
    private StatisticsMapper statisticsMapper;

    /**
     * 页面路径CODE
     */
    private static Map<String, String> pathCodeMap = null;

    @Resource
    private MongoTemplate mongoTemplate;

    @Resource
    private ProductQueryHistoryMapper productQueryHistoryMapper;

    @Resource
    private RedisService redisService;

    Executor pools = new ThreadPoolExecutor(5, 10, 5, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1024 * 1024));

    @Override
    public List<Map> getPathConfig() {
        return statisticsMapper.findAllPageCode();
    }

    @Override
    public List<Map> getPageStatisticsList(String platform, String code, String channel,String startDateStr, String endDateStr) {
        DateTime beginDate = null;
        DateTime endDate = null;
        List<Map> resultData = new ArrayList<>();
        if (StringUtils.isNotBlank(startDateStr) && StringUtils.isNotBlank(endDateStr)) {
            beginDate = DateUtil.parse(startDateStr, "yyyy-MM-dd");
            endDate = DateUtil.parse(endDateStr, "yyyy-MM-dd");
            long between = DateUtil.between(beginDate, endDate, DateUnit.DAY);
            if (between > 31) {
                throw new ApiException("查询时间跨度不能超过31天");
            }
        } else {
            throw new ApiException("开始时间和结束时间不能为空！");
        }
        String key = "pageStatisticsList:" + platform + ":" + code + ":" + channel + ":" + startDateStr + ":" + endDateStr;
        //跨时间查询直接走缓存
        if (org.apache.commons.lang.StringUtils.isNotBlank(startDateStr) && !startDateStr.equals(endDateStr)) {
            String value = redisService.get(key);
            if(StringUtils.isNotEmpty(value)){
                return JSON.parseArray(value, Map.class);
            }
        }
        List<Map> pathConfig = getPathConfig();
        List<String> codeList = new ArrayList<>();
        if (StringUtils.isNotEmpty(code)) {
            codeList = Arrays.asList(code);
        } else {
            if (!CollectionUtils.isEmpty(pathConfig)) {
                codeList = pathConfig.stream().map(map -> map.get("code").toString()).collect(Collectors.toList());
            }
        }

        try {
            CountDownLatch countDownLatch = new CountDownLatch(codeList.size());
            DateTime finalBeginDate = beginDate;
            DateTime finalEndDate = endDate;
            for (String itemCode : codeList) {
                pools.execute(() -> {
                    try {
                        Criteria criteria = new Criteria();
                        String collectionName = null;
                        Map<String,Object> dataMap = new HashMap<>();
                        //初始化数据
                        dataMap.put("_id",null);
                        dataMap.put("count",null);
                        dataMap.put("browseTime",null);
                        dataMap.put("channel",null);
                        dataMap.put("platform",null);
                        dataMap.put("page",null);
                        dataMap.put("queryParamList",new ArrayList<>());
                        dataMap.put("successCount",null);
                        dataMap.put("failCount",null);
                        //如果起始时间和截止时间为同一天，则查询日集合
                        if (org.apache.commons.lang.StringUtils.isNotBlank(startDateStr) && startDateStr.equals(endDateStr)) {
                            if (StringUtils.isNotBlank(startDateStr)) {
                                collectionName = "page:" + startDateStr;
                            } else {
                                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
                                collectionName = "page:" + sdf.format(new Date());
                            }
                            criteria = Criteria.where("dayTime").is(startDateStr);
                        } else {
                            collectionName = MongoCollectionNameConstants.PAGE_STATISTICS;
                            criteria = Criteria.where("dayTime").gte(startDateStr).lte(endDateStr);
                        }
                        if (StringUtils.isNotEmpty(platform)) {
                            criteria.and("platform").is(platform);
                        }
                        if (StringUtils.isNotEmpty(channel)) {
                            criteria.and("channel").is(channel);
                        }
                        if (StringUtils.isNotEmpty(itemCode)) {
                            criteria.and("pathCode").is(itemCode);
                        }
                        Aggregation aggregation = Aggregation.newAggregation(
                                Aggregation.match(criteria),
                                Aggregation.group().count().as("count")
                                        .avg("browseTime").as("browseTime")
                                        .last("channel").as("channel")
                                        .last("platform").as("platform")
                                        .last("pathCode").as("pathCode")
                                        .last("toPage").as("toPage").addToSet("uuid").as("uniqueUuids"),
                                Aggregation.project("count", "browseTime", "channel", "platform", "pathCode", "toPage").and("uniqueUuids").size().as("num").andExclude("_id")
                        );



                        Map results = mongoTemplate.aggregate(aggregation, collectionName, Map.class).getUniqueMappedResult();

                        //测试
                        Aggregation aggregation2 = Aggregation.newAggregation(
                                Aggregation.match(criteria),
                                Aggregation.group("uuid"),
                                Aggregation.count().as("num"));
                        Map results2 = mongoTemplate.aggregate(aggregation2, collectionName, Map.class).getUniqueMappedResult();

                        Map pageMap = pathConfig.stream().filter(item -> item.get("code").equals(itemCode)).findAny().orElse(null);
                        if(results != null){
                            dataMap = results;
                        }
                        dataMap.put("page", pageMap.get("descr"));
                        if ("SEARCH_RESULT".equals(itemCode) && null != results) {
                            try {
                                List<ProductQueryHistory> productQueryHistories = productQueryHistoryMapper.queryByParams(platform, channel, finalBeginDate, finalEndDate);
                                if (!CollectionUtils.isEmpty(productQueryHistories)) {
                                    long successCount = 0;
                                    long failCount = 0;
                                    List<String> queryParamList = productQueryHistories.stream().filter(item -> StringUtils.isNotBlank(item.getQueryParam())).map(ProductQueryHistory::getQueryParam).distinct().collect(Collectors.toList());
                                    Map<String, List<ProductQueryHistory>> successMap = productQueryHistories.stream().filter(item -> item.getStatus().equals(1)).collect(Collectors.groupingBy(ProductQueryHistory::getUuid));
                                    if (!CollectionUtils.isEmpty(successMap)) {
                                        successCount = successMap.keySet().size();
                                    }
                                    Map<String, List<ProductQueryHistory>> failMap = productQueryHistories.stream().filter(item -> item.getStatus().equals(2)).collect(Collectors.groupingBy(ProductQueryHistory::getUuid));
                                    if (!CollectionUtils.isEmpty(failMap)) {
                                        failCount = failMap.keySet().size();
                                    }
                                    results.put("queryParamList", queryParamList);
                                    results.put("successCount", successCount);
                                    results.put("failCount", failCount);
                                }
                            } catch (Exception e) {
                                log.error("查询搜索页统计数据错误");
                            }
                        }
                        resultData.add(dataMap);
                    } catch (Exception e) {
                        log.error("页面统计多线程查询出错{}", e);
                    } finally {
                        countDownLatch.countDown();
                    }
                });
            }
            countDownLatch.await();
        } catch (Exception e) {
            log.error("页面统计查询出错{}",e);
        }
        if (org.apache.commons.lang.StringUtils.isNotBlank(startDateStr) && !startDateStr.equals(endDateStr)) {
            redisService.set(key, JSON.toJSONString(resultData), 2 * 60);
        }
        return resultData;
    }

    /**
     * @param platform
     * @param timeType 1日 2周 3月
     * @param channel
     * @return
     */
    @Override
    public PageStatisticsResponse getPageStatistics(String platform, Integer timeType, String channel) {

        if(timeType == 1) {
            return getToday(platform,channel);
        }
        if(timeType == 2 || timeType == 3){
            return getWeekAndMonth(platform,timeType,channel);
        }

        return null;
    }

    /**
     * 获取浏览数
     *
     * @param response
     * @param type
     * @param platform
     * @param channel
     * @param startTime
     * @param endTime
     */
    public void getBrowseNum(PageStatisticsResponse response,Integer type, String platform, String channel, Date startTime, Date endTime,String dayTime) {
        Criteria criteria = dayTime == null ? getCriteria(platform, channel, null, startTime, endTime)  : getCriteriaByDayTime(platform, channel, null, null);
        Query query = Query.query(criteria);
        long count = mongoTemplate.count(query, MongoCollectionNameConstants.PAGE_STATISTICS);
        int uuid = mongoTemplate.findDistinct(query, "uuid", MongoCollectionNameConstants.PAGE_STATISTICS, PageStatistics.class).size();
        response.setBrowseNum(count);
        response.setVisitors(uuid);
        response.setAvgBrowseNum(uuid == 0 || count == 0 ? 0 : count/uuid);
        Long a = type == 1 ? 1L :type == 2 ? 7L : 30L;
        response.setAvgVisitors(response.getAvgVisitors()/a);
    }


    /**
     * 获取 周月 统计表
     * @param platform
     * @param timeType
     * @param channel
     * @return
     */
    public PageStatisticsResponse getWeekAndMonth(String platform, Integer timeType, String channel) {
        LocalDate localDate = null;
        if (timeType == 2) {
            localDate = LocalDate.now().minusDays(7);
        } else {
            localDate = LocalDate.now().minusDays(30);
        }
        //获取当前时间段
        Date startTime = CalendarUtil.dateFormatParse(localDate);
//        LocalDateTime localDateTime = localDate.atStartOfDay().minusHours(8);
//        Date startTime = Date.from( localDateTime.atZone( ZoneId.systemDefault()).toInstant());
        Date endTime = CalendarUtil.getEndTime(CalendarUtil.dateFormatParse(LocalDate.now().minusDays(1)));
        PageStatisticsResponse weekAndMonthDetail = getWeekAndMonthDetail(platform, channel, startTime, endTime, localDate, timeType);
        //设置浏览数
        getBrowseNum(weekAndMonthDetail,timeType,platform,channel,startTime,endTime,null);
        return weekAndMonthDetail;
    }


    /**
     * 获取周月统计数据
     *
     * @param platform
     * @param channel
     * @param startTime
     * @param endTime
     * @return
     */
    public PageStatisticsResponse getWeekAndMonthDetail(String platform, String channel, Date startTime, Date endTime, LocalDate localDate, Integer type) {
        //商城页
        List<Map> discover = aggregateByDayTime(getCriteria(platform, channel, "DISCOVER", startTime, endTime), localDate, type);
        //首页
        List<Map> home = aggregateByDayTime(getCriteria(platform, channel, "HOME", startTime, endTime), localDate, type);
        //购物车
        List<Map> cart = aggregateByDayTime(getCriteria(platform, channel, "SHOPPING_CART", startTime, endTime), localDate, type);
        //发布
        List<Map> release = aggregateByDayTime(getCriteria(platform, channel, "RELEASE", startTime, endTime), localDate, type);
        //我的
        List<Map> my = aggregateByDayTime(getCriteria(platform, channel, "MY", startTime, endTime), localDate, type);

        //秒杀
        List<Map> seckill = aggregateByDayTime(getCriteria(platform, channel, "SECKILL", startTime, endTime), localDate, type);
        //积分
        List<Map> score = aggregateByDayTime(getCriteria(platform, channel, "SCORE_ACTIVITY", startTime, endTime), localDate, type);
        //优惠券
        List<Map> coupon = aggregateByDayTime(getCriteria(platform, channel, "COUPON_ACTIVITY", startTime, endTime), localDate, type);
        //旧换新
        List<Map> old = aggregateByDayTime(getCriteria(platform, channel, "OLD_FOR_NEW", startTime, endTime), localDate, type);

        return new PageStatisticsResponse(discover, home, cart, release, my, seckill, score, coupon, old);
    }

    /**
     * 获取今日页面统计
     *
     * @param platform
     * @param channel
     * @return
     */
    public PageStatisticsResponse getToday(String platform, String channel) {


        //获取当前时间段
        String dayTime = CalendarUtil.dateFormat(new Date());

        //商城页
        List<Map> discover = aggregateByHourTime(getCriteriaByDayTime(platform, channel, "DISCOVER", dayTime));
        //首页
        List<Map> home = aggregateByHourTime(getCriteriaByDayTime(platform, channel, "HOME", dayTime));
        //购物车
        List<Map> cart = aggregateByHourTime(getCriteriaByDayTime(platform, channel, "SHOPPING_CART", dayTime));
        //发布
        List<Map> release = aggregateByHourTime(getCriteriaByDayTime(platform, channel, "RELEASE", dayTime));
        //我的
        List<Map> my = aggregateByHourTime(getCriteriaByDayTime(platform, channel, "MY", dayTime));

        //秒杀
        List<Map> seckill = aggregateByHourTime(getCriteriaByDayTime(platform, channel, "SECKILL", dayTime));
        //积分
        List<Map> score = aggregateByHourTime(getCriteriaByDayTime(platform, channel, "SCORE_ACTIVITY", dayTime));
        //优惠券
        List<Map> coupon = aggregateByHourTime(getCriteriaByDayTime(platform, channel, "COUPON_ACTIVITY", dayTime));
        //旧换新
        List<Map> old = aggregateByHourTime(getCriteriaByDayTime(platform, channel, "OLD_FOR_NEW", dayTime));

        PageStatisticsResponse pageStatisticsResponse = new PageStatisticsResponse(discover, home, cart, release, my, seckill, score, coupon, old);

        getBrowseNum(pageStatisticsResponse,1,platform,channel,null,null,dayTime);

        return pageStatisticsResponse;
    }

    /**
     * 获取路径CODE
     *
     * @param page
     * @return
     */
    public String getPathCode(String page) {
        if (pathCodeMap == null) {
            pathCodeMap = Maps.newHashMap();
            List<Map> allPageCode = statisticsMapper.findAllPageCode();
            if (allPageCode != null) {
                for (Map map : allPageCode) {
                    pathCodeMap.put(map.get("path").toString(), map.get("code").toString());
                }
            }
        }
        return pathCodeMap.get(page);
    }


    /**
     * 按天分组查询
     *
     * @param criteria
     * @param localDate
     * @param type
     * @return
     */
    public List<Map> aggregateByDayTime(Criteria criteria, LocalDate localDate, Integer type) {
        Aggregation aggregation = Aggregation.newAggregation(
                Aggregation.match(criteria),
                Aggregation.group("dayTime", "uuid").sum("browseTime").as("browseTime"),
                Aggregation.group("dayTime").count().as("count").sum("browseTime").as("browseTime").last("dayTime").as("dayTime")
        );
        AggregationResults<Map> results = mongoTemplate.aggregate(aggregation, MongoCollectionNameConstants.PAGE_STATISTICS, Map.class);
        return dataHandle(results.getMappedResults(), localDate, type);
    }

    /**
     * 按天分组查询 数据出理 缺失时间处理
     *
     * @param data
     * @param localDate
     * @param type
     * @return
     */
    public List<Map> dataHandle(List<Map> data, LocalDate localDate, Integer type) {
        Integer num;
        if (type == 2) {
            num = 7;
        } else {
            num = 30;
        }

        Long browseTime = 0L;
        Long countAll = 0L;
        List<Map> resultList = new ArrayList<>();
        for (int i = 0; i < num; i++) {
            Map map = new HashMap();
            int finalI = i;
            LocalDate time = localDate.plusDays(i);
            map.put("time", time+"");
            map.put("num", finalI + 1);
            Map dayTime = data.stream().filter(item -> item.get("dayTime").equals(time+"")).findAny().orElse(null);
            if (dayTime != null) {
                long count = new BigDecimal(dayTime.get("count")+"").longValue();
                long sum = new BigDecimal(dayTime.get("browseTime")+"").longValue();
                map.put("count", count);
                countAll = countAll + count;
                browseTime = browseTime + sum;
            } else {
                map.put("count", 0);
            }
            resultList.add(map);
        }
        resultList.get(0).put("countAll",countAll);
        resultList.get(0).put("browseTime",browseTime);
        return resultList;
    }


    /**
     * 按小时分组 查询数据
     *
     * @param criteria
     * @return
     */
    public List<Map> aggregateByHourTime(Criteria criteria) {

        Aggregation aggregation = Aggregation.newAggregation(
                Aggregation.match(criteria),
                Aggregation.group("hourTime", "uuid").sum("browseTime").as("browseTime"),
                Aggregation.group("hourTime").count().as("count").last("hourTime").as("hourTime")
                        .sum("browseTime").as("browseTime")
        );
        AggregationResults<Map> results = mongoTemplate.aggregate(aggregation, PageStatistics.class, Map.class);

        List<Map> mappedResults = results.getMappedResults();

        Long browseTime = 0L;
        Long countAll = 0L;
        List<Map> resultList = new ArrayList<>();
        for (int i = 0; i < 24; i++) {
            Map map = new HashMap();
            int finalI = i;
            map.put("time", finalI);
            map.put("num", finalI + 1);
            Map hourTime = mappedResults.stream().filter(item -> item.get("hourTime").equals(finalI)).findAny().orElse(null);
            if (hourTime != null) {
                long count = new BigDecimal(hourTime.get("count")+"").longValue();
                long sum = new BigDecimal(hourTime.get("browseTime")+"").longValue();
                map.put("count", count);
                countAll = countAll + count;
                browseTime = browseTime + sum;
            } else {
                map.put("count", 0);
            }
            resultList.add(map);
        }
        resultList.get(0).put("countAll",countAll);
        resultList.get(0).put("browseTime",browseTime);
        return resultList;
    }

    /**
     * 获取查询条件
     *
     * @param platform
     * @param channel
     * @param pathCode
     * @param startTime
     * @param endTime
     * @return
     */
    public Criteria getCriteria(String platform, String channel, String pathCode, Date startTime, Date endTime) {
        Criteria criteria = Criteria.where("createTime").gte(startTime).lte(endTime);
        if (StringUtils.isNotEmpty(platform)){
            if (platform.equals("app")) {
                criteria.and("platform").is("android");
            } else {
                criteria.and("platform").is(platform);
            }
        }
        if (StringUtils.isNotEmpty(channel))
            criteria.and("channel").is(channel);
        if(StringUtils.isNotEmpty(pathCode))
            criteria.and("pathCode").is(pathCode);
        return criteria;
    }
    public Criteria getCriteriaByDayTime(String platform, String channel, String pathCode, String dayTime) {
        Criteria criteria = Criteria.where("dayTime").is(dayTime);
        if (StringUtils.isNotEmpty(platform)){
            if (platform.equals("app")) {
                criteria.and("platform").is("android");
            } else {

                criteria.and("platform").is(platform);
            }
        }
        if (StringUtils.isNotEmpty(channel))
            criteria.and("channel").is(channel);
        if(StringUtils.isNotEmpty(pathCode))
            criteria.and("pathCode").is(pathCode);
        return criteria;
    }

    public Criteria getCriteriaByDayTimeNew(String platform, String channel, String pathCode, String dayTime) {
        Criteria criteria = Criteria.where("dayTime").is(dayTime);
        if (StringUtils.isNotEmpty(platform)){
            criteria.and("platform").is(platform);
        }
        if (StringUtils.isNotEmpty(channel)) {
            criteria.and("channel").is(channel);
        }

        if(StringUtils.isNotEmpty(pathCode)) {
           criteria.and("pathCode").is(pathCode);
        }
        return criteria;
    }
}
